/*****************************************************************************/
/*                                                                           */
/*    Domino Operation System Driver Module                                  */
/*                                                                           */
/*    Copyright (C) 2007 Laszlo Arvai                                        */
/*                                                                           */
/*    ------------------------------------------------------------------     */
/*    drvHD44780.c - Hitachi Character LCD controller driver for dsPIC       */
/*****************************************************************************/

#include <krnlTypes.h>
#include <krnlSystemTimer.h>
#include "sysSettings.h"
#include "sysCompiler.h"
#include "drvIOConfig.h"

///////////////////////////////////////////////////////////////////////////////
// Local functions
static void drvHD44780ShortDelay(void);
static void drvHD44780PulseE(void);
static dosBool drvHD44780IsBusy(void);
static void drvHD44780WriteByte( dosByte in_byte );
static void drvHD44780WriteCommand( dosByte in_command );
static void drvHD44780WriteData( dosByte in_data );

static dosByte drvHD44780ReadAddressCounter(void);
static void WaitForLCD(void);

///////////////////////////////////////////////////////////////////////////////
// Local variables
#if lcdNUMBER_OF_LINES > 1
static dosROM_DATA( dosByte, l_line_address[4] ) =
{
	0x00,
	0x40,
	0x14,
	0x54
};
#endif

#if !defined(LCDD4IsSet) || !defined(LCDD5IsSet) || !defined(LCDD6IsSet) || !defined(LCDD7IsSet) || !defined(LCDRW)
static dosByte l_address = 0;
#endif

///////////////////////////////////////////////////////////////////////////////
// Initialize character LCD driver
void drvLCDInit(void)
{
	// init data pins	
	LCDD4(1);
	LCDD5(1);
	LCDD6(1);
	LCDD7(1);

	InitLCDD4(OUTPUT_PIN);
	InitLCDD5(OUTPUT_PIN);
	InitLCDD6(OUTPUT_PIN);
	InitLCDD7(OUTPUT_PIN);

	// init pins
	InitLCDRS();
	InitLCDRW();
	InitLCDE();

	LCDE(0);	
	LCDRS(0);
	
#ifdef LCDRW
	LCDRW(0);
#endif
	
	///////////////////////////
	// init display controller
	
	// Power on delay
	krnlDelay(200);
	
	// setup 4-bit interface
	LCDD4(1);
	LCDD5(1);
	LCDD6(0);
	LCDD7(0);
	
	// wait 15ms
	drvHD44780PulseE();				// 1st
	krnlDelay(20);
	
	// wait
	drvHD44780PulseE();				// 2nd
	krnlDelay(20);

	// wait
	drvHD44780PulseE();				// 2nd
	krnlDelay(20);

	// Set function (high nibble)
	LCDD4(0);
	LCDD5(1);
	LCDD6(0);
	LCDD7(0);

	// wait
	drvHD44780PulseE(); // (send high nibble)
	krnlDelay(20);

	// from now we can read busy flag
	
	// function setup (again)
	drvHD44780WriteCommand(0x28);
	krnlDelay(20);
	
	// display off
	drvHD44780WriteCommand(0x08);
	
	// display clear
	drvHD44780WriteCommand(0x01);
	
	// entry mode set
	drvHD44780WriteCommand(0x06);
	
	// display home
	drvHD44780WriteCommand(0x02);

	// display on
	drvHD44780WriteCommand(0x0c);
}

///////////////////////////////////////////////////////////////////////////////
// Display Clear
void drvLCDClearDisplay(void)
{
	drvHD44780WriteCommand(0x01);
}

///////////////////////////////////////////////////////////////////////////////
// Moves cursor to the specified coordinates
dosBool drvLCDGotoXY( dosByte in_x, dosByte in_y )
{
	dosByte address = 0;
	dosByte success = dosTrue;
	
	// coordinates starts from 1
	in_y--;
	in_x--;

	// check if coordinates within range
	if( in_y < lcdNUMBER_OF_LINES && in_x < lcdNUMBER_OF_CHARACTERS )
	{
#if lcdNUMBER_OF_LINES > 1
		address = l_line_address[in_y] + in_x;
#else
		if( in_x < 8 )
			address = in_x;
		else
			address = in_x + 0x40 - 8;
#endif
	}
	else
		success = dosFalse;
	
	
#if !defined(LCDD4IsSet) || !defined(LCDD5IsSet) || !defined(LCDD6IsSet) || !defined(LCDD7IsSet) || !defined(LCDRW)
	l_address = address;
#endif

	// send address
	drvHD44780WriteCommand( 0x80 + address );
	
	return success;
}	

///////////////////////////////////////////////////////////////////////////////
// Write character
void drvLCDWriteCharacter( dosChar in_char )
{
	drvHD44780WriteData( in_char );
	
#ifndef LCDRW
	l_address++;
#endif

	// update address in the case of one line display
	#if lcdNUMBER_OF_LINES == 1
	if( drvHD44780ReadAddressCounter() == 8 )
	{
		drvHD44780WriteCommand( 0x80 | 0x40 );
	}
	#endif
}
 
///////////////////////////////////////////////////////////////////////////////
// Load custom characters
void drvLCDDowloadCustomCharacters( const dosByte* in_characters )
{
	dosByte i;
	
	// set CG RAM address
	drvHD44780WriteCommand( 0x40 );
	
	for( i = 0; i < 64; i++ )
		drvHD44780WriteData( in_characters[i] );
	
	// reset DD RAM address
	drvHD44780WriteCommand( 0x80 );
}

///////////////////////////////////////////////////////////////////////////////
// download custom character
void drvLCDDowloadCustomCharacter( dosByte in_index, const dosByte* in_characters )
{
	dosByte i;
	
	// set CG RAM address
	drvHD44780WriteCommand( 0x40 + 8 * in_index );
	
	for( i = 0; i < 8; i++ )
		drvHD44780WriteData( in_characters[i] );
	
	// reset DD RAM address
	drvHD44780WriteCommand( 0x80 );
}


///////////////////////////////////////////////////////////////////////////////
// Local functions
///////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////
// Short delay > 300us
static void drvHD44780ShortDelay(void)
{
#ifdef LCD_PULSE_LENGTH
	dosWord i;
	
	for(i = 0; i < LCD_PULSE_LENGTH; i++ )
		sysNOP();
#endif

#ifdef LCD_SHORT_DELAY_LOOP
	LCD_SHORT_DELAY_LOOP;
#endif
}

///////////////////////////////////////////////////////////////////////////////
// Pulse enable pin
static void drvHD44780PulseE(void)
{
	drvHD44780ShortDelay(); // pin setup delay
	LCDE(1);
	drvHD44780ShortDelay(); // pulse
	LCDE(0);
	drvHD44780ShortDelay();
}

///////////////////////////////////////////////////////////////////////////////
// Is busy LCD
static dosBool drvHD44780IsBusy(void)
{
#ifdef LCDRW	
	dosBool busy;
	
	// change pin directions
	InitLCDD4(INPUT_PIN);
	InitLCDD5(INPUT_PIN);
	InitLCDD6(INPUT_PIN);
	InitLCDD7(INPUT_PIN);
	
	// setup control pins
	LCDRW(1);
	LCDRS(0);
	
	// RW RS setup delay
	drvHD44780ShortDelay();
	
	// read flag
	LCDE(1);
	drvHD44780ShortDelay();
	
	busy = LCDD7IsSet();
	
	LCDE(0);
	drvHD44780ShortDelay();
	
	// read lower 4 bits
	LCDE(1);
	drvHD44780ShortDelay();
	LCDE(0);
	drvHD44780ShortDelay();

	return busy;
#else
	krnlDelay(2); // 2ms delay

	return dosFalse;
#endif
}

///////////////////////////////////////////////////////////////////////////////
// Read address counter
static dosByte drvHD44780ReadAddressCounter(void)
{
#if defined(LCDD4IsSet) &&  defined(LCDD5IsSet) &&  defined(LCDD6IsSet) &&  defined(LCDD7IsSet) && defined(LCDRW)
	dosByte address = 0;
	
	// change pin directions
	InitLCDD4(INPUT_PIN);
	InitLCDD5(INPUT_PIN);
	InitLCDD6(INPUT_PIN);
	InitLCDD7(INPUT_PIN);
	
	// setup control pins
	LCDRW(1);
	LCDRS(0);
	
	// RW RS setup delay
	drvHD44780ShortDelay();
	
	// read flag
	LCDE(1);
	drvHD44780ShortDelay();
	
	if( LCDD6IsSet() )
		address |= (1<<6);

	if( LCDD5IsSet() )
		address |= (1<<5);

	if( LCDD4IsSet() )
		address |= (1<<4);

	LCDE(0);
	drvHD44780ShortDelay();
	
	// read lower 4 bits
	LCDE(1);
	drvHD44780ShortDelay();

	if( LCDD7IsSet() )
		address |= (1<<3);

	if( LCDD6IsSet() )
		address |= (1<<2);

	if( LCDD5IsSet() )
		address |= (1<<1);

	if( LCDD4IsSet() )
		address |= (1<<0);

	LCDE(0);
	drvHD44780ShortDelay();

	return address;
#else
	return l_address;
#endif	
}

///////////////////////////////////////////////////////////////////////////////
// Write  byte
static void drvHD44780WriteByte( dosByte in_byte )
{
	// init pins
#ifdef LCDRW
	LCDRW(0);
#endif
	InitLCDD4(OUTPUT_PIN);
	InitLCDD5(OUTPUT_PIN);
	InitLCDD6(OUTPUT_PIN);
	InitLCDD7(OUTPUT_PIN);
	
	// send high nibble
	LCDD7( (in_byte >> 7) & 1 );
  LCDD6( (in_byte >> 6) & 1 );
	LCDD5( (in_byte >> 5) & 1 );
	LCDD4( (in_byte >> 4) & 1 );
	
	drvHD44780PulseE();
	
	// send low nibble
	LCDD7( (in_byte >> 3) & 1 );
  LCDD6( (in_byte >> 2) & 1 );
	LCDD5( (in_byte >> 1) & 1 );
	LCDD4( (in_byte     ) & 1 );

	drvHD44780PulseE();	
	
	// change direction to input
	InitLCDD4(INPUT_PIN);
	InitLCDD5(INPUT_PIN);
	InitLCDD6(INPUT_PIN);
	InitLCDD7(INPUT_PIN);
}

///////////////////////////////////////////////////////////////////////////////
// Write command
static void drvHD44780WriteCommand( dosByte in_command )
{
	// wait if display is busy
	WaitForLCD();

	// select command register
	LCDRS(0);

	// write command byte
	drvHD44780WriteByte( in_command );
}

///////////////////////////////////////////////////////////////////////////////
// Write data
static void drvHD44780WriteData( dosByte in_data )
{
	// wait if display is busy
	WaitForLCD();

	// init pins
	LCDRS(1);
	
	// write data byte
	drvHD44780WriteByte( in_data );
}

///////////////////////////////////////////////////////////////////////////////
// Wait if LCD is busy
static void WaitForLCD(void)
{
	dosWord start_time = krnlGetSystemTimer();
	dosWord diff_time;

	// wait (with timeout) if display is busy
	do
	{
		diff_time = krnlGetSystemTimer() - start_time;
	}	while( drvHD44780IsBusy() && diff_time < 2 );
}
